#include "../common/common.h"

// 打印传输进度
void print_progress(long current, long total) {//current：表示当前已经完成的任务量，total：表示任务的总量,类型均为 long
    //计算当前进度的百分比
    int percent = (int)((double)current / total * 100);//current 和 total 转换为浮点数进行除法运算，确保结果是浮点数
    //打印进度条的开头
    printf("\rProgress: %d%% [", percent);//\r：回车符，将光标移回到当前行的开头，这样每次打印进度条时都会覆盖上一次的内容，而不是换行
    for (int i = 0; i < 50; i++) {//循环50次，表示进度条的长度为50个字符
        if (i < percent / 2) {//判断当前循环的索引是否小于百分比的一半（因为进度条长度为50，而百分比是0-100，所以用 percent / 2
            printf("=");//打印 =，表示已完成的部分
        } else {
            printf(" ");//打印空格"  "，表示未完成的部分
        }
    }
    //打印进度条的结尾部分
    printf("] %ld/%ld bytes", current, total);//%ld/%ld bytes：打印当前完成的字节数和总字节数，%ld 是格式占位符，表示长整数（long）
    fflush(stdout);//使用 fflush(stdout) 刷新标准输出缓冲区，确保进度条能够立即显示到屏幕上，而不是等待缓冲区满后再输出
    if (current >= total) {//如果 current 大于或等于 total，表示任务已经完成
        printf("\n");//打印一个换行符 \n，将光标移动到下一行，以便后续的输出不会覆盖进度条
    }
}

// 上传文件到服务器
void upload_file(int sockfd, const char *filename) {//filename：表示要上传的文件名，类型为 const char *，即指向字符数组的指针
    //准备传输数据
    TransferData data;//定义了一个 TransferData 类型的变量 data，用于存储要发送给服务器的命令和文件名
    data.cmd = CMD_UPLOAD;//data.cmd = CMD_UPLOAD：设置命令类型为 CMD_UPLOAD，表示这是一个文件上传请求
    strncpy(data.filename, filename, MAX_FILENAME);//将文件名复制到 data.filename 中，最多复制 MAX_FILENAME 个字符，防止溢出
    send(sockfd, &data, sizeof(data), 0);//通过套接字 sockfd 将 data 发送到服务器，sizeof(data) 表示发送的数据大小

    FILE *file = fopen(filename, "rb");//使用 fopen 函数以二进制读取模式（"rb"）打开文件
    if (file == NULL) {
        perror("File not found");
        long filesize = -1;
        send(sockfd, &filesize, sizeof(filesize), 0);//将 filesize 发送到服务器，通知服务器文件打开失败
        return;
    }

    // 获取文件大小
    fseek(file, 0, SEEK_END);//使用 fseek(file, 0, SEEK_END) 将文件指针移动到文件末尾
    long filesize = ftell(file);//使用 ftell(file) 获取文件的总大小，存储到 filesize 中
    fseek(file, 0, SEEK_SET);//使用 fseek(file, 0, SEEK_SET) 将文件指针重新移动到文件开头
    send(sockfd, &filesize, sizeof(filesize), 0);//使用 send 将文件大小 filesize 发送到服务器

    // 发送文件内容
    char buffer[BUFFER_SIZE];//定义一个缓冲区 buffer，大小为 BUFFER_SIZE，用于存储从文件中读取的数据
    long total_sent = 0;//定义 total_sent 用于记录已发送的字节数
    size_t bytes_read;//定义 bytes_read 用于存储每次从文件中读取的字节数

    while ((bytes_read = fread(buffer, 1, BUFFER_SIZE, file)) != 0) {//从文件中读取最多 BUFFER_SIZE 个字节到 buffer 中，返回实际读取的字节数
        send(sockfd, buffer, bytes_read, 0);//使用 send 将缓冲区中的数据发送到服务器
        total_sent += bytes_read;//更新 total_sent，累加已发送的字节数
        print_progress(total_sent, filesize);//调用 print_progress 函数显示当前的上传进度
    }

    fclose(file);//使用 fclose 关闭文件，释放文件资源
}

// 从服务器下载文件
void download_file(int sockfd, const char *filename) {
    TransferData data;//定义了一个 TransferData 类型的变量 data，用于存储下载请求的命令和文件名
    data.cmd = CMD_DOWNLOAD;//设置命令类型为 CMD_DOWNLOAD，表示这是一个文件下载请求
    strncpy(data.filename, filename, MAX_FILENAME);//将文件名复制到 data.filename 中，最多复制 MAX_FILENAME 个字符，防止溢出
    send(sockfd, &data, sizeof(data), 0);//通过套接字 sockfd 将 data 发送到服务器，请求下载文件

    long filesize;//定义一个 long 类型变量 filesize，用于存储从服务器接收到的文件大小
    recv(sockfd, &filesize, sizeof(filesize), 0);//使用 recv 函数从服务器接收文件大小信息，sizeof(filesize) 表示接收的数据大小

    if (filesize == -1) {//如果 filesize 为 -1，表示服务器上没有找到该文件
        printf("File not found on server\n");
        return;
    }

    FILE *file = fopen(filename, "wb");//使用 fopen 函数以二进制写入模式（"wb"）打开（或创建）本地文件
    if (file == NULL) {//打开文件失败
        perror("Failed to create file");
        return;
    }

    char buffer[BUFFER_SIZE];//定义一个缓冲区 buffer，大小为 BUFFER_SIZE，用于存储从服务器接收的数据
    long total_received = 0;//定义 total_received 用于记录已接收的字节数
    ssize_t bytes_received;//定义 bytes_received 用于存储每次从服务器接收的字节数

    while (total_received < filesize) {//
        bytes_received = recv(sockfd, buffer, BUFFER_SIZE, 0);//从服务器接收最多 BUFFER_SIZE 个字节到 buffer 中，返回实际接收到的字节数
        if (bytes_received <= 0) {//表示接收结束（可能是因为网络错误或文件传输完成）
            break;
        }
        fwrite(buffer, 1, bytes_received, file);//使用 fwrite(buffer, 1, bytes_received, file) 将接收到的数据写入本地文件
        total_received += bytes_received;//更新 total_received，累加已接收的字节数
        print_progress(total_received, filesize);//调用 print_progress 函数显示当前的下载进度
    }

    fclose(file);//使用 fclose 关闭本地文件，释放文件资源
}

// 列出服务器上的文件
void list_server_files(int sockfd) {
    TransferData data;//定义了一个 TransferData 类型的变量 data，用于存储要发送给服务器的命令
    data.cmd = CMD_LIST;//data.cmd = CMD_LIST：设置命令类型为 CMD_LIST，表示这是一个列出文件的请求
    send(sockfd, &data, sizeof(data), 0);//通过套接字 sockfd 将 data 发送到服务器，请求列出服务器上的文件

    char file_list[BUFFER_SIZE];//定义了一个字符数组 file_list，大小为 BUFFER_SIZE，用于存储从服务器接收到的文件列表
    recv(sockfd, file_list, BUFFER_SIZE, 0);//使用 recv 函数从服务器接收文件列表，BUFFER_SIZE 表示接收的最大字节数
    printf("Server files:\n%s", file_list);//使用 printf 函数输出服务器上的文件列表
}

// 显示服务器连接信息
void show_server_connections(int sockfd) {
    TransferData data;//定义一个 TransferData 类型的变量 data
    data.cmd = CMD_CONNECTIONS;//设置 data.cmd 为 CMD_CONNECTIONS，表示这是一个请求服务器连接信息的命令
    send(sockfd, &data, sizeof(data), 0);//使用 send 函数通过套接字 sockfd 将请求数据发送到服务器
                        //sizeof(data) 表示发送的数据大小
    char conn_info[BUFFER_SIZE];//定义一个字符数组 conn_info，大小为 BUFFER_SIZE，用于存储从服务器接收到的连接信息
    recv(sockfd, conn_info, BUFFER_SIZE, 0);//使用 recv 函数从服务器接收连接信息，BUFFER_SIZE 表示接收的最大字节数
    printf("Server connections:\n%s", conn_info);//使用 printf 函数输出服务器的连接信息
}

// 连接服务器
int connect_to_server(const char *ip, int port) {
    int sockfd;//定义一个整数变量 sockfd，用于存储套接字文件描述符
    struct sockaddr_in serv_addr;//定义一个 struct sockaddr_in 类型的变量 serv_addr，用于存储服务器的地址信息

    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {//AF_INET：表示使用 IPv4 地址族,SOCK_STREAM：表示使用 TCP 协议,0：默认协议（TCP）
        perror("Socket creation error");
        return -1;
    }

    serv_addr.sin_family = AF_INET;//设置 serv_addr.sin_family 为 AF_INET，表示使用 IPv4 地址族
    serv_addr.sin_port = htons(port);//使用 htons 函数将端口号从主机字节序转换为网络字节序，并存储到 serv_addr.sin_port 中

    if (inet_pton(AF_INET, ip, &serv_addr.sin_addr) <= 0) {//使用 inet_pton 函数将点分十进制的 IP 地址字符串（如 "192.168.1.1"）转换为网络字节序的二进制格式，并存储到 serv_addr.sin_addr 中
        perror("Invalid address/ Address not supported");
        return -1;
    }
    //建立连接：
    if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {//使用 connect 函数尝试与服务器建立连接
        perror("Connection Failed");
        return -1;
    }

    return sockfd;//如果连接成功，返回套接字文件描述符 sockfd，供后续通信使用
}

// 客户端主菜单
void client_menu(int sockfd) {
    int choice;//用于存储用户的选择
    char filename[MAX_FILENAME];//用于存储用户输入的文件名，最大长度为 MAX_FILENAME

    while (1) {
        printf("\n文件传输客户端\n");
        printf("1. 上传文件\n");
        printf("2. 下载文件\n");
        printf("3. 列出服务器文件\n");
        printf("4. 显示服务器连接信息\n");
        printf("5. 退出\n");
        printf("请输入您的选择: ");
        scanf("%d", &choice);
        getchar(); // 清除输入缓冲区

        switch (choice) {
            case 1:
                printf("请输入要上传的文件名: ");
                fgets(filename, MAX_FILENAME, stdin);//使用 fgets 读取用户输入的文件名
                filename[strcspn(filename, "\n")] = '\0'; //使用 strcspn 移除文件名中的换行符
                upload_file(sockfd, filename);//调用 upload_file 函数上传文件
                break;
            case 2:
                printf("请输入要下载的文件名: ");
                fgets(filename, MAX_FILENAME, stdin);
                filename[strcspn(filename, "\n")] = '\0';
                download_file(sockfd, filename);
                break;
            case 3:
                list_server_files(sockfd);
                break;
            case 4:
                show_server_connections(sockfd);
                break;
            case 5:
                printf("退出中...\n");
                TransferData data;
                data.cmd = CMD_EXIT;
                send(sockfd, &data, sizeof(data), 0);
                return;//退出函数
            default:
                printf("无效的选择\n");
                break;
        }
    }
}

int main() {
    //定义服务器的 IP 地址和端口号
    const char *server_ip = "192.168.1.60";
    int port = 8080;

    int sockfd = connect_to_server(server_ip, port);//调用 connect_to_server 函数尝试连接到服务器
    if (sockfd < 0) {
        return -1;
    }

    printf("Connected to server %s:%d\n", server_ip, port);//提示用户已成功连接到服务器
    client_menu(sockfd);//调用 client_menu 函数，进入交互式菜单

    close(sockfd);//在程序退出前，关闭与服务器的连接
    return 0;
}
